package YAPserver.logic;

import java.util.HashMap;
import java.util.concurrent.ConcurrentLinkedQueue;

import YAPenvironment.YAPconstant;
import YAPserver.ClientHandler;
import YAPserver.GameActualizer;
//import YAPserver.HighScore;
import YAPserver.Lobby;
import YAPserver.logic.Input.MoveCommand;

/**
 *
 * @author Daniel
 * This class handles the setup of the game and runs the game.
 */
public class Game extends Thread{
	private GamePlay m_gamePlay;
	private HashMap<Integer, Player> m_players = new HashMap<Integer,Player>();
	private Field m_field;
	static public double m_fps = YAPconstant.C_FPS_LIMIT; //frames per second
//	static public double m_fps = 1000;
	//TODO: link Output with GameActualizer
	private Output m_out = new Output();
	private Input m_in;
	private int m_lives = YAPconstant.C_LIVES;
	private Lobby lobby;
	
	private boolean paused=false;
	
	/**
	 * Constructs a game with a field.
	 * @param numOfEdges number of edges the field should have
	 */
	public Game(int numOfEdges){
		m_field = new Field(numOfEdges, m_out);
	}
	
	/**
	 * Constructs a game with a queue of players and a link to the lobby that the players belong to.
	 * A field is also generated
	 * @param players concurrentLinkedQueue of clientHandlers who will play the game
	 * @param numOfEdges number of edges the field should have
	 * @param lobby lobby that the players are in
	 */
	public Game(ConcurrentLinkedQueue<ClientHandler> players, int numOfEdges, Lobby lobby){
		m_field = new Field(numOfEdges, m_out);
		this.lobby = lobby;
	}
	
	/**
	 * Sets the field.
	 * @param numOfEdges number of edges the field should have
	 * @param radReg a double between 0 and 1 with 0 being regular and 1 most irregular
	 * @param regular a boolean if set to true creates a regular field regardless of radreg
	 * @see Field
	 */
	public void setField(int numOfEdges, double radReg, boolean regular){
		m_field.changeField(numOfEdges, radReg, regular);
	}
	
	/**
	 * Sets the hashMap<Integer, Player> of players.
	 * @param players a hashMap<Integer, Player> of players.
	 */
	public void setPlayers(HashMap<Integer,Player> players){
		m_players = players;
	}
	
	/**
	 * Creates a HashMap of players out of the concurrentLinkedQueue<ClientHandler> of clients
	 * @param clients a concurrentLinkedQueue<ClientHandler> of clients
	 */
	public void setPlayers(ConcurrentLinkedQueue<ClientHandler> clients){
		m_players = new HashMap<Integer,Player>();
		for(ClientHandler client : clients){
			m_players.put(client.getGameId(), new Player(client.getGameId(), client.getName(), m_out));
		}
		// end result: m_players defined
	}
	
	/**
	 * Sets a new gameModel and creates a new gamePlay.
	 * Note that it is important that the hashMap of players has already been created.
	 * @param gameModel a standardAbstract gameModel that the game will use to play.
	 * @param playersSliders a HashMap<Integer, Integer> with the key being the player's id and the value the player's chosen wall
	 * @see #setPlayers()
	 */
	public void setGameModel(AbstractModel gameModel, HashMap<Integer, Integer> playersSliders){
		//by this time m_players should be defined
		//default is StandardModel()
		try{
			m_in = new Input(m_players);
			m_gamePlay=new GamePlay(gameModel, m_players, playersSliders, m_field, m_in, m_out);
			for(int id: m_gamePlay.getPlayers().keySet()){
				m_gamePlay.getPlayer(id).setLives(m_lives);
			}
		}catch(NullPointerException e){
			//nullPointerException if m_players is undefined or m_field hasn't been initialised
			//do something so people know there's an exception...
		}	
	}
	
	/**
	 * Links this game's output with this gameActualizer
	 * @param actual - GameActualizer actual to be linked to Output
	 */
	public void setOutput(GameActualizer actual){
		m_out.linkActualiser(actual);
	}
	
	/**
	 * 
	 * @return this game's gamePlay
	 */
	public GamePlay getGameModel(){
		return this.m_gamePlay;
	}
	
	@Override
	public void run(){
		int winningId = -1;
		while(m_gamePlay.hasNotEnded()){
			while(!paused){
				winningId = m_gamePlay.playOneRound();
				try{
					sleep(Math.round(1000/Game.m_fps));
				}catch (Exception e){
					//ignore
				}
				if(!m_gamePlay.hasNotEnded())
					break;
			}
			try{
				Thread.sleep(100);
			}catch(Exception e){
				//ignore, dont care NANANANANANAN
			}
		}
//		System.out.println("game ended");
//		for(int id: m_players.keySet()){
//			winningId = id;
//		}
		
		lobby.endGame(winningId);
		
	}
	
	/**
	 * Pauses and unpauses the game
	 */
	public void pause(){
		if(this.paused)
			this.paused=false;
		else
			this.paused=true;
	}
	
	/**
	 * Retaurns the pause state of the Game.
	 * @return boolean if the Game is paused or not
	 */
	public boolean isPaused(){
		return this.paused;
	}
	
	/**
	 * 
	 * @return the corners of the playing field
	 */
	public String getField(){
		return m_out.getFieldCorners();
	}
	
	/**
	 * 
	 * @return all object positions in the field
	 */
	public String getObjectPos(){
		return m_out.getObjectPos();
	}
	
	/**
	 * 
	 * @return how many lives a player has
	 */
	public String getPlayerStat(){
		return m_out.getPlayerStat();
	}
	
	/**
	 * 
	 * @return the endscore of the game
	 */
	public String getScore(){
		return m_out.getHighScore();
	}
	
	/**
	 * Sets the MoveCommand of a player. Either as left or right.
	 * @param id - id of the player
	 * @param command - MoveCommand to be set
	 */
	public void setCommand(int id, MoveCommand command){
		m_in.setCommand(id, command);
	}
	
	/**
	 * Removes the specified player.
	 * @param id - id of the player
	 */
	public void removePlayer(int id){
		//sets this players life to -1 and he will be replaced by a solid wall
		this.m_gamePlay.getPlayer(id).setLives(-1);
	}
	
	/**
	 * Sets how many lives each player will start with when the game starts.
	 * @param lives - lives each player with which each player will start.
	 */
	public void setLives(int lives) {
		m_lives = lives;
	}

	/**
	 * 
	 * @return how many lives each player will start with when the game starts
	 */
	public int getLives() {
		return m_lives;
	}
}
